/*
  Copyright (c) 2010
  The Regents of the University of Michigan
  All Rights Reserved

  Permission is granted to use, copy, create derivative works, and
  redistribute this software and such derivative works for any purpose,
  so long as the name of the University of Michigan is not used in
  any advertising or publicity pertaining to the use or distribution
  of this software without specific, written prior authorization. If
  the above copyright notice or any other identification of the
  University of Michigan is included in any copy of any portion of
  this software, then the disclaimer below must also be included.

  This software is provided as is, without representation or warranty
  of any kind either express or implied, including without limitation
  the implied warranties of merchantability, fitness for a particular
  purpose, or noninfringement.  The Regents of the University of
  Michigan shall not be liable for any damages, including special,
  indirect, incidental, or consequential damages, with respect to any
  claim arising out of or in connection with the use of the software,
  even if it has been or is hereafter advised of the possibility of
  such damages.
*/

#include "id.h"

#include "contact.h"

#include <string.h>

#include <openssl/sha.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <glib.h>


gboolean clearview_id_equal(gconstpointer lhs, gconstpointer rhs) {
  return memcmp(lhs, rhs, SHA_DIGEST_LENGTH) == 0 ? TRUE : FALSE;
}


gint clearview_id_cmp(gconstpointer lhs, gconstpointer rhs, gpointer junk) {
  return memcmp(lhs, rhs, SHA_DIGEST_LENGTH);
}

char *get_id_distance(const char *id1, const char *id2) {
  char *ret = clearview_id_new();
  char *r = ret;
  const char *p = id1;
  const char *q = id2;
  int ii;

  for (ii = 0; ii != KEY_SIZE; ++ii) {
    *r++ = *p++ ^ *q++;
  }

  return ret;
}

gint clearview_id_shared_prefix_bits(const unsigned char *id1,
                                     const unsigned char* id2) {
  static const unsigned int LEADING_ZEROS[256] = {8, 7, 6, 6, 5, 5, 5, 5, 4, 4,
                                                  4, 4, 4, 4, 4, 4, 3, 3, 3, 3,
                                                  3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
                                                  3, 3, 2, 2, 2, 2, 2, 2, 2, 2,
                                                  2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
                                                  2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
                                                  2, 2, 2, 2, 1, 1, 1, 1, 1, 1,
                                                  1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                                                  1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                                                  1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                                                  1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                                                  1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                                                  1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                                                  0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                  0, 0, 0, 0, 0, 0};
  int ii;
  gint ret = 0;

  for (ii = 0; ii != KEY_SIZE; ++ii) {
    if (id1[ii] == id2[ii]) {
      ret += 8;
    } else {
      return ret + LEADING_ZEROS[id1[ii] ^ id2[ii]];
    }
  }

  return ret;
}

gint clearview_id_cmp_by_distance(gconstpointer lhs, gconstpointer rhs,
                                  gpointer reference) {
  const unsigned char *id1 = lhs, *id2 = rhs, *ref = reference;
  int ii;

  for (ii = 0; ii != KEY_SIZE; ++ii) {
    int dist1 = id1[ii] ^ ref[ii];
    int dist2 = id2[ii] ^ ref[ii];
    int diff = dist1 - dist2;
    if (diff) {
      return diff;
    }
  }
  
  return 0;
}


gint clearview_cmp_contacts_by_distance(gconstpointer contact1,
                                        gconstpointer contact2,
                                        gpointer reference) {
  char *contact_id1 = clearview_get_id((struct ContactAddress*)contact1);
  char *contact_id2 = clearview_get_id((struct ContactAddress*)contact2);

  gint ret = clearview_id_cmp_by_distance(contact_id1, contact_id2, reference);
  return ret;
}


guint clearview_id_hash(gconstpointer key)  {
  // The ID is the output of SHA-1, which is already a nice hash.
  // Why try harder?
  return *((guint *)key);
}

char *clearview_id_copy(char *id) {
  return g_slice_copy(KEY_SIZE, id);
}


static inline int numlen(unsigned char num) {
  if (num < 10) {
    return 1;
  } else if (num < 100) {
    return 2;
  } else {
    return 3;
  }
}

static inline char *inet_ntoa_fast(struct in_addr in) {
  static char buf[18];
  static const char *nums[256] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41","42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", "130", "131", "132", "133", "134", "135", "136", "137", "138", "139", "140", "141", "142", "143", "144", "145", "146", "147", "148", "149", "150", "151", "152", "153","154", "155", "156", "157", "158", "159", "160", "161", "162", "163", "164", "165", "166", "167", "168", "169", "170", "171", "172", "173", "174", "175", "176", "177", "178", "179", "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", "190", "191", "192", "193", "194", "195", "196", "197", "198", "199", "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", "210", "211", "212", "213", "214", "215", "216", "217", "218", "219", "220", "221", "222", "223", "224", "225", "226", "227", "228", "229", "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242", "243", "244", "245", "246", "247", "248", "249", "250", "251", "252", "253", "254", "255"};
  char *p = buf;
  unsigned char *bytes = (unsigned char *) &in;
  strcpy(p, nums[bytes[0]]);
  p += numlen(bytes[0]);
  *p++ = '.';

  strcpy(p, nums[bytes[1]]);
  p += numlen(bytes[1]);
  *p++ = '.';

  strcpy(p, nums[bytes[2]]);
  p += numlen(bytes[2]);
  *p++ = '.';

  strcpy(p, nums[bytes[3]]);
  p += numlen(bytes[3]);
  *p = '\0';
  return buf;
}

// Return the ID of the host with the given address, allocated on the heap.
char *clearview_get_id(struct ContactAddress *contact) {
  // It's the SHA-1 of the IP *string* + ":" + the port *string*.
  char to_hash[32] = {0};  // xxx.xxx.xxx.xxx + : + 65535 + \0 = 22; make it big
  unsigned char *hashed;

  int addr_end;
  
  if (contact->id) {
    return contact->id;
  }

//  g_assert(strcmp(inet_ntoa(contact->addr), inet_ntoa_fast(contact->addr)) == 0);
  strcpy(to_hash, inet_ntoa_fast(contact->addr));

  addr_end = strlen(to_hash);
  g_assert(addr_end <= 15);

  to_hash[addr_end++] = ':';

  g_snprintf(to_hash+addr_end, sizeof(to_hash) - addr_end, "%d", contact->port);
  hashed = (unsigned char*)clearview_id_new();

  SHA1((unsigned char*)to_hash, strlen(to_hash), hashed);
  return contact->id = (char*)hashed;
}


